﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity.ObjectBuilder;
using Castle.DynamicProxy;

namespace Simon.Unity.Proxy
{   
   public class ProxyContainerExtension : UnityContainerExtension
   {      
      protected override void Initialize()
      {
         Context.Strategies.Add(new ProxyWrapUpStrategy(), UnityBuildStage.PostInitialization);
      }      

      public void RegisterInterceptor(CreateInterceptorDelegate interceptorCreator, Type targetType, int priority)                  
      {
         IInterceptorCreationPolicy policy;

         if (targetType != null)
         {
            NamedTypeBuildKey buildKey = new NamedTypeBuildKey(targetType);
            policy = Context.Policies.GetNoDefault<IInterceptorCreationPolicy>(buildKey, true);
            if (policy == null)
            {
               policy = new InterceptorCreationPolicy();
               Context.Policies.Set<IInterceptorCreationPolicy>(policy, buildKey);
            }
         }
         else
         {
            policy = Context.Policies.Get<IInterceptorCreationPolicy>(null, true);
            if (policy == null)
            {
               policy = new InterceptorCreationPolicy();
               Context.Policies.SetDefault<IInterceptorCreationPolicy>(policy);
            }
         }
         policy.RegisterInterceptor(interceptorCreator, priority);
      }
            
      private sealed class InterceptorCreationPolicy : IInterceptorCreationPolicy
      {
         private readonly List<ListItem> _items = new List<ListItem>();
         private List<ListItem> _sortedItems = new List<ListItem>();

         public IEnumerable<IInterceptor> CreateInterceptors(NamedTypeBuildKey buildKey)
         {
            lock (_items)
            {
               if (_sortedItems == null)
               {
                  _sortedItems = new List<ListItem>(_items);
                  _sortedItems.Sort();
               }
               return _sortedItems.Select<ListItem, IInterceptor>(item => item.Callback(buildKey));
            }
         }

         public void RegisterInterceptor(CreateInterceptorDelegate interceptorCreator, int priorty)         
         {
            lock (_items)
            {
               _items.Add(new ListItem { Priority = priorty, Callback = interceptorCreator });
               _sortedItems = null;
            }
         }

         private sealed class ListItem : IComparable<ListItem>
         {
            public int Priority { get; set; }
            public CreateInterceptorDelegate Callback { get; set; }

            #region IComparable<ListItem> Members
            public int CompareTo(ListItem other)
            {
               return -(Priority.CompareTo(other.Priority));
            }
            #endregion
         }
      }

      private sealed class ProxyWrapUpStrategy : BuilderStrategy
      {
         private ProxyGenerator _proxyGen = new ProxyGenerator();

         public override void PostBuildUp(IBuilderContext context)
         {            
            if (context.OriginalBuildKey is NamedTypeBuildKey)
            {
               NamedTypeBuildKey namedKey = (NamedTypeBuildKey)context.OriginalBuildKey;
               if (namedKey.Type.IsInterface)
               {
                  IInterceptorCreationPolicy policy = context.Policies.Get<IInterceptorCreationPolicy>(context.BuildKey);                  
                  if (policy != null)
                  {
                     IEnumerable<IInterceptor> interceptors = policy.CreateInterceptors(namedKey);
                     interceptors = interceptors.Select<IInterceptor, IInterceptor>(inter =>
                        (IInterceptor)context.Strategies.ExecuteBuildUp(PrepareContext(context.Existing, context, new NamedTypeBuildKey(inter.GetType()), inter)));

                     object current = context.Existing;
                     foreach (IInterceptor inter in interceptors)
                     {
                        current = _proxyGen.CreateProxy(namedKey.Type, inter, current);
                     }
                     context.Existing = current;
                  }
               }
            }
         }

         public override void PreTearDown(IBuilderContext context)
         {
            if (context.OriginalBuildKey is NamedTypeBuildKey)
            {
               NamedTypeBuildKey namedKey = (NamedTypeBuildKey)context.OriginalBuildKey;
               if (namedKey.Type.IsInterface)
               {
                  IInterceptorCreationPolicy policy = context.Policies.Get<IInterceptorCreationPolicy>(context.BuildKey);
                  if (policy != null)
                  {
                     int a = 2;
                  }
               }
            }
         }

         private IBuilderContext PrepareContext(object currentObject, IBuilderContext currentContext, object newBuildKey, object newExistingObject)
         {
            IBuilderContext clone = currentContext.CloneForNewBuild(newBuildKey, newExistingObject);
            clone.Policies.SetDefault<IParentGetterPolicy>(new ParentGetterPolicy(currentObject));
            return clone;
         }
      }
   }
}
